import { Callout } from '@/components'

# createGetQueryClient

<Callout type='experimental'>

`createGetQueryClient` is an experimental feature, so this interface may change.

</Callout>

A utility function to manage QueryClient instances in a server-safe manner.

- **On the server**: Always creates a new QueryClient instance for each request
- **In the browser**: Returns a singleton QueryClient instance, creating one if it doesn't exist

This pattern is essential for proper React Query usage with SSR frameworks like Next.js, as it prevents sharing QueryClient state between requests on the server while maintaining a single instance in the browser to preserve cache across re-renders.

<Callout type='important'>

Sharing a QueryClient between requests on the server can lead to **serious security vulnerabilities**. `getQueryClient` creates a new QueryClient instance for each request to prevent data leakage between users and exposure of sensitive information. See the [Server-Side Behavior](#server-side-behavior) section below for details.

</Callout>

## Basic Usage

First, create a `get-query-client.ts` file in your project:

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

export const { getQueryClient } = createGetQueryClient()
```

Then, import and use it in your code:

```tsx
// app/providers.tsx
'use client'

import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools />
    </QueryClientProvider>
  )
}
```

## Configuration

You can pass a `QueryClientConfig` to `createGetQueryClient` to customize the QueryClient instance:

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

export const { getQueryClient } = createGetQueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5000,
      retry: 3,
    },
  },
})
```

```tsx
// app/providers.tsx
'use client'

import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools />
    </QueryClientProvider>
  )
}
```

<Callout type='warning'>

**Browser Environment Behavior**

In the browser environment, the config provided when calling `createGetQueryClient` is used to create the QueryClient instance. The `getQueryClient` function does not accept any arguments and will always return the same singleton instance. This is intentional to maintain a stable singleton across re-renders.

</Callout>

## Server-Side Behavior

### Preventing Security Issues (Highest Priority)

Sharing a QueryClient between requests on the server can lead to serious security vulnerabilities:

- **Data leakage between users**: One user's data may be exposed to another user
- **Sensitive information exposure**: Authentication tokens, personal information, etc. may be included in another user's request
- **Incorrect cache state**: Cached data from a previous request may be incorrectly returned to the next request

`getQueryClient` creates a new QueryClient instance for each request to completely prevent these security issues.

> 💡 **Related documentation**: For more information about why QueryClient should not be shared between requests on the server, see the [TanStack Query SSR Guide - Initial Setup](https://tanstack.com/query/latest/docs/framework/react/guides/ssr#initial-setup) section.

### Setting Cache Removal Time to Prevent OOM

On the server, `getQueryClient` sets the cache removal time to `Infinity` to prevent memory leaks and OOM (Out of Memory) errors. This is an additional safety measure to address memory issues that can occur when servers handle many concurrent requests.

`gcTime` (v5) or `cacheTime` (v4) represents the time until TanStack Query removes query data from the QueryClient's cache. By setting this to `Infinity` on the server:

- Data is not automatically removed from the cache, keeping it available until the request completes
- The [`isValidTimeout`](https://github.com/TanStack/query/blob/main/packages/query-core/src/utils.ts) check in TanStack Query prevents `setTimeout` scheduling in [`scheduleGc`](https://github.com/TanStack/query/blob/main/packages/query-core/src/removable.ts#L13-L21), completely eliminating the performance cost that would occur with many concurrent requests

This behavior is **automatic and cannot be overridden** - even if you provide a different cache removal time value in your config, it will be set to `Infinity` on the server. (In v4, the `cacheTime` option is used; in v5, the `gcTime` option is used. See the Version Differences section below for details.)

> 💡 **Related documentation**: For more information about high memory consumption on server and why `gcTime` defaults to `Infinity` on the server, see the [TanStack Query SSR Guide - High memory consumption on server](https://tanstack.com/query/latest/docs/framework/react/guides/ssr#high-memory-consumption-on-server) section.

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

// Config is passed to createGetQueryClient, not getQueryClient
export const { getQueryClient } = createGetQueryClient({
  defaultOptions: {
    queries: {
      // On server, cache removal time is set to Infinity
      // to keep cache alive until the request completes and prevent setTimeout scheduling
      gcTime: 5000, // v5: This will be overridden to Infinity on server
      // In v4, cacheTime is used and also overridden to Infinity
    },
  },
})

// On server: Cache removal time is always Infinity (OOM prevention)
// On browser: Uses the provided value (or default)
```

### New Instance Per Request

On the server, `getQueryClient` creates a new QueryClient instance for each call. This ensures that:

- Each request has isolated cache state
- No data leaks between different user requests
- Proper cleanup when the request completes

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

export const { getQueryClient } = createGetQueryClient()
```

```tsx
// Server environment
const queryClient1 = getQueryClient()
const queryClient2 = getQueryClient()

// queryClient1 !== queryClient2 (different instances)
```

## Browser-Side Behavior

### Singleton Pattern

In the browser, `getQueryClient` returns the same QueryClient instance across multiple calls. This ensures:

- Cache is preserved across re-renders
- Consistent state management
- Optimal performance

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

export const { getQueryClient } = createGetQueryClient()
```

```tsx
// Browser environment
const queryClient1 = getQueryClient()
const queryClient2 = getQueryClient()

// queryClient1 === queryClient2 (same instance)
```

## Use Cases

### Next.js App Router

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

export const { getQueryClient } = createGetQueryClient()
```

```tsx
// app/providers.tsx
'use client'

import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools />
    </QueryClientProvider>
  )
}
```

```tsx
// app/layout.tsx
import { Providers } from './providers'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}
```

### With Custom Configuration

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

export const { getQueryClient } = createGetQueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutes
      gcTime: 1000 * 60 * 10, // 10 minutes (v5)
      // cacheTime: 1000 * 60 * 10, // 10 minutes (v4)
      retry: 2,
      refetchOnWindowFocus: false,
    },
    mutations: {
      retry: 1,
    },
  },
})
```

```tsx
// app/providers.tsx
'use client'

import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools />
    </QueryClientProvider>
  )
}
```

## Version Differences

### @tanstack/react-query v4

In v4, use the `createGetQueryClient` function, and the cache time option is called `cacheTime`:

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

export const { getQueryClient } = createGetQueryClient()
```

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

// Server: cacheTime is automatically set to Infinity
export const { getQueryClient } = createGetQueryClient({
  defaultOptions: {
    queries: {
      cacheTime: 5000, // Overridden to Infinity on server
    },
  },
})
```

```tsx
// app/providers.tsx
'use client'

import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools />
    </QueryClientProvider>
  )
}
```

### @tanstack/react-query v5

In v5, use the `createGetQueryClient` function, and the cache time option is called `gcTime` (garbage collection time):

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

export const { getQueryClient } = createGetQueryClient()
```

```tsx
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'

// Server: gcTime is automatically set to Infinity
export const { getQueryClient } = createGetQueryClient({
  defaultOptions: {
    queries: {
      gcTime: 5000, // Overridden to Infinity on server
    },
  },
})
```

```tsx
// app/providers.tsx
'use client'

import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools />
    </QueryClientProvider>
  )
}
```

## Important Notes

- **Server safety**: Always use `getQueryClient` instead of creating `new QueryClient()` directly in SSR environments to ensure proper request isolation and OOM prevention.

<Callout type='error'>

**Never use QueryClient as a global variable**

Even if you use `getQueryClient`, storing it in a global variable like `export const queryClient = getQueryClient()` in App Router and importing it elsewhere will cause the same security risks. The QueryClient instance will be shared between requests on the server, leading to data leakage between users. Always call `getQueryClient()` directly where you need it, rather than storing it in a global variable.

</Callout>

- **Browser singleton**: The browser instance is a singleton. Configure your QueryClient when calling `createGetQueryClient`, as `getQueryClient` does not accept any arguments.

- **Automatic OOM prevention**: The cache removal time override to `Infinity` on the server is automatic and cannot be disabled. This is a safety feature to prevent memory issues. (In v4, the `cacheTime` option is used; in v5, the `gcTime` option is used.)
